package cn.spring;

import java.io.File;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class MyAnnotationApplicationContext {
    private Class configClass;
    private Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();
    private Map<String, Object> singletonMap = new HashMap<>();

    public MyAnnotationApplicationContext(Class configClass) {
        this.configClass = configClass;
        // scan
        scan();
    }

    private void scan() {
        // @ComponentScan
        ComponentScan componentScanAnnotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
        if (componentScanAnnotation != null) {
            String scanPath = componentScanAnnotation.value(); // com.tuling.service
            scanPath = scanPath.replace(".", "/");
            URL resource = this.getClass().getClassLoader().getResource(scanPath);
            File path = new File(resource.getFile());
            if (path.isDirectory()) {
                File[] files = path.listFiles();
                for (File file : files) {
                    String name = file.getName();
                    String clazzName = scanPath.replace("/", ".") + "." + name.replace(".class", "");
                    try {
                        Class<?> beanClass = this.getClass().getClassLoader().loadClass(clazzName);


                        // @Component
                        if (beanClass.isAnnotationPresent(Component.class)) {
                            BeanDefinition beanDefinition = new BeanDefinition();

                            beanDefinition.setType(beanClass);

                            Component componentAnnotation = beanClass.getAnnotation(Component.class);
                            String beanName = componentAnnotation.value();

                            beanDefinition.setName(beanName);

                            // @Scope
                            if (beanClass.isAnnotationPresent(Scope.class)) {
                                Scope scopeAnnotation = beanClass.getAnnotation(Scope.class);
                                String scope = scopeAnnotation.value();
                                beanDefinition.setScope(scope);
                            } else {
                                beanDefinition.setScope("singleton");
                            }

                            beanDefinitionMap.put(beanDefinition.getName(), beanDefinition);
                        }

                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }

                }
            }
        }
    }

    public Object getBean(String beanName) {
        if (beanDefinitionMap.size() <= 0) {
            throw new RuntimeException("找不到Bean：" + beanName);
        }

        if (singletonMap.get(beanName) != null) {
            return singletonMap.get(beanName);
        }

        Iterator<Map.Entry<String, BeanDefinition>> iterator = beanDefinitionMap.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, BeanDefinition> next = iterator.next();
            String bName = next.getKey();
            BeanDefinition beanDefinition = next.getValue();

            Object singletonBean = null;
            try {
                singletonBean = createBean(beanDefinition);
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }


            if (beanDefinition.getScope().equals("singleton")) {
                singletonMap.put(bName, singletonBean);
            } else {
                return singletonBean;
            }
        }

        return singletonMap.get(beanName);
    }

    private Object createBean(BeanDefinition beanDefinition) throws IllegalAccessException, InstantiationException {
        Object instance = beanDefinition.getType().newInstance();

        // Autowired
        Field[] declaredFields = beanDefinition.getType().getDeclaredFields();
        for (Field declaredField : declaredFields) {
            if (declaredField.isAnnotationPresent(Autowired.class)) {
                String name = declaredField.getName();
                declaredField.setAccessible(true);
                declaredField.set(instance, getBean(name));
            }
        }

        return instance;
    }
}
